﻿using System;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.Practices.Unity.Configuration.ConfigurationHelpers;

using Unity;
using Unity.Injection;
using Unity.Interception;
using Unity.Interception.PolicyInjection;
using Unity.Interception.InterceptionBehaviors;
using Unity.Interception.ContainerIntegration;
using Unity.Interception.Interceptors.InstanceInterceptors.InterfaceInterception;

using pri.smilly.QuickStart.Aop;
using pri.smilly.QuickStart.Ioc;

namespace pri.smilly.QuickStart.Config {
    public class ContainerConfigurator : IConfigurator {
        private IUnityContainer container;
        private List<Assembly> assemblies;
        private Interception interceptConfigor;

        private List<BeanWrapper> beans = new List<BeanWrapper>();
        private List<PolicyWrapper> policies = new List<PolicyWrapper>();

        public ContainerConfigurator(IUnityContainer container, params Assembly[] assemblies) {
            this.container = container;
            this.assemblies = new List<Assembly>(assemblies.Length);
            foreach (Assembly assembly in assemblies) {
                this.assemblies.Add(assembly);
            }
        }

        public void Config() {
            interceptConfigor = container.AddNewExtension<Interception>().Configure<Interception>();
            AnalysisBeanTypes();
            AutoRegistBeans();
            AutoRegistInterceps();
        }

        private void AnalysisBeanTypes() {
            assemblies.ForEach(assembly => ResolveAssembly(assembly));
        }

        private void ResolveAssembly(Assembly assembly) {
            foreach (Type type in assembly.GetTypes()) {
                ResolveType(type);
            }
        }

        private void ResolveType(Type type) {
            foreach (Attribute attr in type.GetCustomAttributes()) {
                if (attr is ConfiguratorAttribute) {
                    ResolveConfigMethods(type);
                } else if (attr is BeanAttribute) {
                    var beanAttr = attr as BeanAttribute;
                    beans.Add(new BeanWrapper(beanAttr.Name, beanAttr.Type, type));
                } else if (attr is AspectAttribute) {
                    var policyAttr = attr as AspectAttribute;
                    policies.Add(new PolicyWrapper(policyAttr.Expression, type, policyAttr.Order));
                }
            }
        }

        private void ResolveConfigMethods(Type type) {
            foreach (MethodInfo method in type.GetMethods()) {
                var beanAttr = method.GetCustomAttribute(typeof(BeanAttribute)) as BeanAttribute;
                if (null == beanAttr) continue;

                var methodParams = method.GetParameters();
                object[] args = new object[methodParams.Length];
                for (int i = 0; i < methodParams.Length; i++) {
                    var methodParam = methodParams[i];
                    var dependency = methodParam.GetCustomAttribute(typeof(DependencyAttribute)) as DependencyAttribute;
                    args[i] = container.Resolve(methodParam.ParameterType, dependency?.Name);
                }
                object obj = method.Invoke(Activator.CreateInstance(type), args);
                beans.Add(new BeanWrapper(beanAttr.Name, method.ReturnType, obj));
            }
        }

        private void AutoRegistBeans() {
            foreach (BeanWrapper bean in beans) {
                if (bean.From != null && container.IsRegistered(bean.From, bean.Name)) continue;
                if (container.IsRegistered(bean.MapTo, bean.Name)) continue;
                if (bean.IsInstance) {
                    container.RegisterInstance(bean.From, bean.Name, bean.Instance);
                } else {
                    InjectionMember[] members = new InjectionMember[0];
                    if (bean.IsPonitCut) members = InjectionMembers();
                    container.RegisterType(bean.From, bean.MapTo, bean.Name, bean.LifeTimeManager, members);
                }
            }
        }

        private static InjectionMember[] InjectionMembers() {
            string typeName = typeof(PolicyInjectionBehavior).AssemblyQualifiedName;
            Type type = TypeResolver.ResolveTypeWithDefault(typeName, typeof(IInterceptionBehavior));
            return new InjectionMember[] { new Interceptor<InterfaceInterceptor>(), new DefaultInterceptionBehavior(type) };
        }

        private void AutoRegistInterceps() {
            policies.ForEach(policy => { // regist policy
                var handler = new CallHandler(policy.Order);
                foreach (MethodInfo method in policy.Advice.GetMethods()) {
                    foreach (Attribute attr in method.GetCustomAttributes()) {
                        if (!(attr is HandleAttribute)) continue;

                        if (attr is BeforeHandleAttribute) {
                            handler.BeforeHandle = method;
                        } else if (attr is AfterHandleAttribute) {
                            handler.AfterHandle = method;
                        } else if (attr is ExceptHandleAttribute) {
                            handler.ExceptHandle = method;
                        } else if (attr is FinallyHandleAttribute) {
                            handler.FinallyHandle = method;
                        }
                    }
                }
                interceptConfigor.AddPolicy(policy.Expression).AddMatchingRule(policy.MatchRule).AddCallHandler(handler);
            });
        }
    }
}
